dhcpv4: preparations for iovec usage
authorDavid Härdeman <[email protected]>
Thu, 25 Sep 2025 15:09:08 +0000 (17:09 +0200)
committerÁlvaro Fernández Rojas <[email protected]>
Tue, 21 Oct 2025 17:03:58 +0000 (19:03 +0200)
dhcpv4.c currently builds DHCPv4 messages by lots of repeated calls to the
dhcpv4_put() method, which basically does a whole lot of copies to the stack.
The following set of patches convert dhcpv4.c over to use iovecs, which avoids
almost all of the stack allocations and copies.

The first step is to lay the groundwork by changing dhcpv4_send_reply() and the
corresponding dhcpv6_4o6_send_reply() in dhcpv6.c so that they both take an
iovec instead of a plain buffer. Also introduce the first (very very simple)
iovec for dhcpv4_handle_msg() which just contains the whole packet, plus the
end marker as a separate vector element. It will be filled out further in
subsequent patches.

Signed-off-by: David Härdeman <[email protected]>
Link: https://github.com/openwrt/odhcpd/pull/278
Signed-off-by: Álvaro Fernández Rojas <[email protected]>
src/dhcpv4.c
src/dhcpv6.c
src/odhcpd.h

index dcb23730fce55b376cee7c34070c1455e498379a..3d2ad9850ae2144809a3f7b8a7a2622a08d64afa 100644 (file)
@@ -549,39 +549,33 @@ dhcpv4_lease(struct interface *iface, enum dhcpv4_msg msg, const uint8_t *mac,
        return a;
 }
 
-static int dhcpv4_send_reply(const void *buf, size_t len,
-                            const struct sockaddr *dest, socklen_t dest_len,
-                            void *opaque)
+static ssize_t dhcpv4_send_reply(struct iovec *iov, size_t iov_len,
+                                struct sockaddr *dest, socklen_t dest_len,
+                                void *opaque)
 {
        int *sock = opaque;
+       struct msghdr msg = {
+               .msg_name = dest,
+               .msg_namelen = dest_len,
+               .msg_iov = iov,
+               .msg_iovlen = iov_len,
+       };
 
-       return sendto(*sock, buf, len, MSG_DONTWAIT, dest, dest_len);
+       return sendmsg(*sock, &msg, MSG_DONTWAIT);
 }
 
+enum {
+       IOV_HEADER = 0,
+       IOV_END,
+       IOV_TOTAL
+};
+
 void dhcpv4_handle_msg(void *addr, void *data, size_t len,
                struct interface *iface, _unused void *dest_addr,
                send_reply_cb_t send_reply, void *opaque)
 {
        struct dhcpv4_message *req = data;
-
-       if (iface->dhcpv4 == MODE_DISABLED)
-               return;
-
-       /* FIXME: would checking the magic cookie value here break any clients? */
-
-       if (len < offsetof(struct dhcpv4_message, options) ||
-           req->op != DHCPV4_OP_BOOTREQUEST || req->hlen != ETH_ALEN)
-               return;
-
-       debug("Got DHCPv4 request on %s", iface->name);
-
-       if (!iface->dhcpv4_start_ip.s_addr && !iface->dhcpv4_end_ip.s_addr) {
-               warn("No DHCP range available on %s", iface->name);
-               return;
-       }
-
        int sock = iface->dhcpv4_event.uloop.fd;
-
        struct dhcpv4_message reply = {
                .op = DHCPV4_OP_BOOTREPLY,
                .htype = req->htype,
@@ -595,7 +589,12 @@ void dhcpv4_handle_msg(void *addr, void *data, size_t len,
                .siaddr = iface->dhcpv4_local,
                .cookie = htonl(DHCPV4_MAGIC_COOKIE),
        };
-       memcpy(reply.chaddr, req->chaddr, sizeof(reply.chaddr));
+       uint8_t end_opt = DHCPV4_OPT_END;
+
+       struct iovec iov[IOV_TOTAL] = {
+               [IOV_HEADER] = { &reply, 0 },
+               [IOV_END] = { &end_opt, sizeof(end_opt) },
+       };
 
        uint8_t *cursor = reply.options;
        uint8_t reqmsg = DHCPV4_MSG_REQUEST;
@@ -614,6 +613,24 @@ void dhcpv4_handle_msg(void *addr, void *data, size_t len,
        uint8_t *end = ((uint8_t *)data) + len;
        struct dhcpv4_option *opt;
 
+       if (iface->dhcpv4 == MODE_DISABLED)
+               return;
+
+       /* FIXME: would checking the magic cookie value here break any clients? */
+
+       if (len < offsetof(struct dhcpv4_message, options) ||
+           req->op != DHCPV4_OP_BOOTREQUEST || req->hlen != ETH_ALEN)
+               return;
+
+       memcpy(reply.chaddr, req->chaddr, sizeof(reply.chaddr));
+
+       debug("Got DHCPv4 request on %s", iface->name);
+
+       if (!iface->dhcpv4_start_ip.s_addr && !iface->dhcpv4_end_ip.s_addr) {
+               warn("No DHCP range available on %s", iface->name);
+               return;
+       }
+
        dhcpv4_for_each_option(start, end, opt) {
                if (opt->type == DHCPV4_OPT_MESSAGE && opt->len == 1)
                        reqmsg = opt->data[0];
@@ -865,8 +882,6 @@ void dhcpv4_handle_msg(void *addr, void *data, size_t len,
                }
        }
 
-       dhcpv4_put(&reply, &cursor, DHCPV4_OPT_END, 0, NULL);
-
        struct sockaddr_in dest = *((struct sockaddr_in*)addr);
        if (req->giaddr.s_addr) {
                /*
@@ -914,8 +929,9 @@ void dhcpv4_handle_msg(void *addr, void *data, size_t len,
                }
        }
 
-       if (send_reply(&reply, PACKET_SIZE(&reply, cursor),
-                      (struct sockaddr*)&dest, sizeof(dest), opaque) < 0)
+       iov[IOV_HEADER].iov_len = PACKET_SIZE(&reply, cursor);
+
+       if (send_reply(iov, ARRAY_SIZE(iov), (struct sockaddr *)&dest, sizeof(dest), opaque) < 0)
                error("Failed to send %s to %s - %s: %m",
                      dhcpv4_msg_to_string(msg),
                      dest.sin_addr.s_addr == INADDR_BROADCAST ?
index abb0f362b4b9e96232dbd89b09b3dca94844e8e5..8ce1bff9732898861ff49ded6e01725774295c3a 100644 (file)
@@ -253,21 +253,30 @@ struct dhcpv4_msg_data {
        ssize_t len;
 };
 
-static int send_reply(_unused const void *buf, size_t len,
-                     _unused const struct sockaddr *dest, _unused socklen_t dest_len,
-                     _unused void *opaque)
+static ssize_t dhcpv6_4o6_send_reply(struct iovec *iov, size_t iov_len,
+                                    _unused struct sockaddr *dest,
+                                    _unused socklen_t dest_len,
+                                    void *opaque)
 {
        struct dhcpv4_msg_data *reply = opaque;
+       size_t len = 0;
+
+       for (size_t i = 0; i < iov_len; i++)
+               len += iov[i].iov_len;
 
        if (len > reply->maxsize) {
                error("4o6: reply too large, %zu > %zu", len, reply->maxsize);
                reply->len = -1;
-       } else {
-               memcpy(reply->msg, buf, len);
-               reply->len = len;
+               return -1;
+       }
+
+       for (size_t i = 0, off = 0; i < iov_len; i++) {
+               memcpy(reply->msg + off, iov[i].iov_base, iov[i].iov_len);
+               off += iov[i].iov_len;
        }
+       reply->len = len;
 
-       return reply->len;
+       return len;
 }
 
 static ssize_t dhcpv6_4o6_query(uint8_t *buf, size_t buflen,
@@ -301,7 +310,7 @@ static ssize_t dhcpv6_4o6_query(uint8_t *buf, size_t buflen,
        addrv4.sin_port = htons(DHCPV4_CLIENT_PORT);
 
        dhcpv4_handle_msg(&addrv4, msgv4_data, msgv4_len,
-                         iface, NULL, send_reply, &reply);
+                         iface, NULL, dhcpv6_4o6_send_reply, &reply);
 
        return reply.len;
 }
index dc77c03a18ff0ee5bddd94b2356314153ae021d9..dd2eb820594224eabcb124aa8a6ba50d971f319f 100644 (file)
@@ -88,9 +88,9 @@ struct odhcpd_event {
        void (*recv_msgs)(struct odhcpd_event *e);
 };
 
-typedef        int (*send_reply_cb_t)(const void *buf, size_t len,
-                              const struct sockaddr *dest, socklen_t dest_len,
-                              void *opaque);
+typedef        ssize_t (*send_reply_cb_t)(struct iovec *iov, size_t iov_len,
+                                  struct sockaddr *dest, socklen_t dest_len,
+                                  void *opaque);
 
 typedef void (*dhcpv6_binding_cb_handler_t)(struct in6_addr *addr, int prefix,
                                            uint32_t pref, uint32_t valid,